#include "json_parser.h"
#include <fstream>
#include <sstream>
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>

using namespace rapidjson;

void parseMetadata(Value &value, JsonMetadata &metadata);
void parseMaterial(Value &value, JsonMaterial &material);
void parseTopology(Value &value, JsonTopology &topology);
void parseAnchor(Value &value, JsonAnchor &anchor);
template <typename T>
void parseArray(Value &value, T array[], int size);
template <>
void parseArray(Value &value, std::string array[], int size);

JsonFile parseBinJson(std::string data)
{
    Document d;
    d.Parse(data.c_str());
    JsonFile file;

    parseMetadata(d["metadata"], file.Metadata);

    Value &materials = d["materials"];
    for (Value &value : materials.GetArray())
    {
        JsonMaterial material;
        parseMaterial(value, material);
        file.Materials.push_back(material);
    }

    if (d.HasMember("buffers"))
    {
        file.Buffers = d["buffers"].GetString();
    }

    if (d.HasMember("topology"))
    {
        parseTopology(d["topology"], file.Topology);
    }

    return file;
}

JsonFile parseBinJsonFile(std::string path)
{
    std::ifstream file(path);
    if (!file.good())
    {
        throw std::string("failed to open: ") + path;
    }
    std::stringstream buffer;
    buffer << file.rdbuf();
    file.close();
    return parseBinJson(buffer.str());
}

void parseMetadata(Value &value, JsonMetadata &metadata)
{
    if (value.HasMember("formatVersion"))
    {
        metadata.FormatVersion = value["formatVersion"].GetDouble();
    }
    if (value.HasMember("sourceFile"))
    {
        metadata.SourceFile = value["sourceFile"].GetString();
    }
    if (value.HasMember("generatedBy"))
    {
        metadata.GeneratedBy = value["generatedBy"].GetString();
    }
    if (value.HasMember("vertices"))
    {
        metadata.Vertices = value["vertices"].GetInt();
    }
    if (value.HasMember("faces"))
    {
        metadata.Faces = value["faces"].GetInt();
    }
    if (value.HasMember("normals"))
    {
        metadata.Normals = value["normals"].GetInt();
    }
    if (value.HasMember("uvs"))
    {
        metadata.UVs = value["uvs"].GetInt();
    }
    if (value.HasMember("materials"))
    {
        metadata.Materials = value["materials"].GetInt();
    }
}

void parseMaterial(Value &value, JsonMaterial &material)
{
    if (value.HasMember("DbgColor"))
    {
        material.DbgColor = value["DbgColor"].GetInt();
    }
    if (value.HasMember("DbgIndex"))
    {
        material.DbgIndex = value["DbgIndex"].GetInt();
    }
    if (value.HasMember("DbgName"))
    {
        material.DbgName = value["DbgName"].GetString();
    }
    if (value.HasMember("colorAmbient"))
    {
        parseArray<double>(value["colorAmbient"], material.ColorAmbient, 3);
    }
    if (value.HasMember("colorDiffuse"))
    {
        parseArray<double>(value["colorDiffuse"], material.ColorDiffuse, 3);
    }
    if (value.HasMember("colorSpecular"))
    {
        parseArray<double>(value["colorSpecular"], material.ColorSpecular, 3);
    }
    if (value.HasMember("illumination"))
    {
        material.Illumination = value["illumination"].GetDouble();
    }
    if (value.HasMember("shading"))
    {
        material.Shading = value["shading"].GetString();
    }
    if (value.HasMember("doubleSided"))
    {
        material.DoubleSided = value["doubleSided"].GetInt();
    }
    if (value.HasMember("mapAlpha"))
    {
        material.MapAlpha = value["mapAlpha"].GetString();
    }
    if (value.HasMember("mapDiffuseWrap"))
    {
        parseArray<std::string>(value["mapDiffuseWrap"], material.MapDiffuseWrap, 2);
    }
    if (value.HasMember("alphaTest"))
    {
        material.AlphaTest = value["alphaTest"].GetDouble();
    }
    if (value.HasMember("mapDiffuse"))
    {
        material.MapDiffuse = value["mapDiffuse"].GetString();
    }
    if (value.HasMember("opticalDensity"))
    {
        material.OpticalDensity = value["opticalDensity"].GetDouble();
    }
    if (value.HasMember("specularCoef"))
    {
        material.SpecularCoef = value["specularCoef"].GetDouble();
    }
}

void parseTopology(Value &value, JsonTopology &topology)
{
    if (value.HasMember("scale"))
    {
        if (value["scale"].IsDouble())
        {
            topology.Scale = value["scale"].GetDouble();
        }
        else if (value["scale"].IsString())
        {
            topology.Scale = atof(value["scale"].GetString());
        }
        else
        {
            topology.Scale = 1.0;
        }
    }
    if (value.HasMember("rotation"))
    {
        parseArray<double>(value["rotation"], topology.Rotation, 4);
    }
    if (value.HasMember("anchorcount"))
    {
        topology.AnchorCount = value["anchorcount"].GetInt();
    }
    if (value.HasMember("anchors"))
    {
        Value &anchors = value["anchors"];
        for (Value &item : anchors.GetArray())
        {
            JsonAnchor anchor;
            parseAnchor(item, anchor);
            topology.Anchors.push_back(anchor);
        }
    }
    if (value.HasMember("offset"))
    {
        parseArray<double>(value["offset"], topology.Offset, 3);
    }
}

void parseAnchor(Value &value, JsonAnchor &anchor)
{
    if (value.HasMember("normal"))
    {
        parseArray<double>(value["normal"], anchor.Normal, 3);
    }
    if (value.HasMember("center"))
    {
        parseArray<double>(value["center"], anchor.Center, 3);
    }
    if (value.HasMember("unit"))
    {
        if (value["unit"].IsDouble())
        {
            anchor.Unit = value["unit"].GetDouble();
        }
        else if (value["unit"].IsString())
        {
            anchor.Unit = atof(value["unit"].GetString());
        }
        else
        {
            anchor.Unit = 1;
        }
    }
    if (value.HasMember("name"))
    {
        anchor.Name = value["name"].GetString();
    }
    if (value.HasMember("name_zh"))
    {
        anchor.NameZh = value["name_zh"].GetString();
    }
}

template <typename T>
void parseArray(Value &value, T array[], int size)
{
    GenericArray<false, Value> list = value.GetArray();
    for (int i = 0; i < size; i++)
    {
        array[i] = list[i].Get<T>();
    }
}

template <>
void parseArray(Value &value, std::string array[], int size)
{
    GenericArray<false, Value> list = value.GetArray();
    for (int i = 0; i < size; i++)
    {
        array[i] = list[i].GetString();
    }
}